package com.zhaoziqian.service.impl;

import com.alibaba.fastjson.JSON;
import com.zhaoziqian.client.CAClient;
import com.zhaoziqian.client.ChannelClient;
import com.zhaoziqian.client.FabricClient;
import com.zhaoziqian.config.Config;
import com.zhaoziqian.config.EnvParam;
import com.zhaoziqian.model.*;
import com.zhaoziqian.service.Invocation;
import com.zhaoziqian.user.UserContext;
import com.zhaoziqian.utils.ReadNetworkConfig;
import com.zhaoziqian.utils.Util;
import org.apache.commons.lang3.StringUtils;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.helper.Utils;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.File;
import java.net.URL;
import java.util.*;

import static com.zhaoziqian.config.Config.*;
import static com.zhaoziqian.config.Config.ADMIN;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Created with fabric-cli
 * 作者： zhaoziqian
 * 时间： 2018/5/28  下午10:57
 * 功能：
 */
@Service
public class InvocationImpl implements Invocation {

    private static final byte[] EXPECTED_EVENT_DATA = "!".getBytes(UTF_8);
    private static final String EXPECTED_EVENT_NAME = "event";
    private Logger logger = LoggerFactory.getLogger(InvocationImpl.class);

    @Override
    public SDKResponse invokeChainCode(String ordererOrg, String ordererId, String peerId, String org, String chainCodeId, String chainCodeVersion, String function, String[] args, String channelName) throws Exception {

        // TODO ： 如果是用的是https协议需要设置prop
        UserContext user = getIdentity(org);
        FabricClient fabClient = new FabricClient(user);

        ChannelClient channelClient = fabClient.createChannelClient(channelName);
        Channel channel = channelClient.getChannel();
        // TODO 需要判断是否使用 grpcs
        PeerModel peerModel = ReadNetworkConfig.getPeer(org, peerId);
        Peer peer = Util.createPeer(fabClient, peerModel);
        // TODO 需要判断是否使用 grpcs
        EventHub eventHub = Util.createEventHub(fabClient, peerModel);

        // TODO 需要判断是否使用 grpcs
        OrdererModel ordererModel = ReadNetworkConfig.getOrderer(ordererOrg, ordererId);
        Orderer orderer = Util.createOrderer(fabClient, ordererModel);

        channel.addPeer(peer);
        channel.addEventHub(eventHub);
        channel.addOrderer(orderer);
        channel.initialize();

        TransactionProposalRequest request = fabClient.getInstance().newTransactionProposalRequest();
        ChaincodeID ccid = ChaincodeID.newBuilder().setName(chainCodeId).build();
        request.setChaincodeID(ccid);
        request.setFcn(function);
        String[] arguments = args;
        request.setArgs(arguments);
        logger.info("####################### Thead id {}  #############################", Thread.currentThread().getId());
        logger.info("######  Will wait {} seconds to complete this transaction  #######", EnvParam.PROPOSAL_WAIT_TIME / 1000);
        logger.info("####################################################################");
        request.setProposalWaitTime(EnvParam.PROPOSAL_WAIT_TIME);

        Map<String, byte[]> tm2 = new HashMap<>();
        tm2.put("HyperLedgerFabric", "TransactionProposalRequest:JavaSDK".getBytes(UTF_8));
        tm2.put("method", "TransactionProposalRequest".getBytes(UTF_8));
        tm2.put("result", ":)".getBytes(UTF_8));
        tm2.put(EXPECTED_EVENT_NAME, EXPECTED_EVENT_DATA);
        request.setTransientMap(tm2);

        Collection<ProposalResponse> responses = channelClient.sendTransactionProposal(request);

        List<Map> responseList = new ArrayList<>();
        for (ProposalResponse res : responses) {
            ChaincodeResponse.Status status = res.getStatus();
            logger.info("Invoked {} on {}. Status - {}", function, chainCodeId, status.getStatus());
            Map item = new HashMap();

            item.put("peerName", res.getPeer().getName());
            item.put("peerUrl", res.getPeer().getUrl());
            item.put("respones_code", res.getStatus().getStatus());
            item.put("messages", res.getMessage());

            logger.debug("------ Respones Peer Name {} -------", res.getPeer().getName());
            logger.debug("------ Respones Peer Url {} -------", res.getPeer().getUrl());
            logger.debug("------ Respones Messages {} -------", res.getMessage());
            logger.debug("------ Respones TransactionID {} -------", res.getTransactionID());

            responseList.add(item);
        }

        logger.info("####################### Thead id {}  #############################", Thread.currentThread().getId());
        logger.info("######           This transaction has been completed         #######");
        logger.info("####################################################################");

        return SDKResponse.OK(JSON.toJSONString(responseList));
    }

    @Override
    public SDKResponse queryChainCode(String ordererOrg, String ordererId, String peerId, String org, String chainCodeId, String chainCodeVersion, String function, String[] args, String channelName) throws Exception {

        UserContext user = getIdentity(org);
        FabricClient fabClient = new FabricClient(user);

        ChannelClient channelClient = fabClient.createChannelClient(channelName);
        Channel channel = channelClient.getChannel();

        // TODO 需要判断是否使用 grpcs
        PeerModel peerModel = ReadNetworkConfig.getPeer(org, peerId);
        Peer peer = Util.createPeer(fabClient, peerModel);

        // TODO 需要判断是否使用 grpcs
        EventHub eventHub = Util.createEventHub(fabClient, peerModel);

        // TODO 需要判断是否使用 grpcs
        OrdererModel ordererModel = ReadNetworkConfig.getOrderer(ordererOrg, ordererId);
        Orderer orderer = Util.createOrderer(fabClient, ordererModel);
        channel.addPeer(peer);
        channel.addEventHub(eventHub);
        channel.addOrderer(orderer);
        channel.initialize();

        logger.info("The query method 【 {} 】 is being executed", function);

        Collection<ProposalResponse> responsesQuery = channelClient.queryByChainCode(chainCodeId, function, args);

        List<Map> responseList = new ArrayList<>();
        for (ProposalResponse pres : responsesQuery) {
            String stringResponse = new String(pres.getChaincodeActionResponsePayload());
            logger.info(stringResponse);
            Map item = new HashMap();

            item.put("peerName", pres.getPeer().getName());
            item.put("peerUrl", pres.getPeer().getUrl());
            item.put("respones_code", pres.getStatus().getStatus());
            item.put("messages", pres.getMessage());
            item.put("result", stringResponse);

            logger.debug("------ Respones Peer Name {} -------", pres.getPeer().getName());
            logger.debug("------ Respones Peer Url {} -------", pres.getPeer().getUrl());
            logger.debug("------ Respones Messages {} -------", pres.getMessage());
            logger.debug("------ Respones TransactionID {} -------", pres.getTransactionID());
            logger.debug("------ Respones ChaincodeActionResponsePayload {} -------", stringResponse);

            responseList.add(item);
        }

        return SDKResponse.OK(JSON.toJSONString(responseList));
    }

    private UserContext getIdentity(String org) throws Exception {

        Util.cleanUp();

        String caUrl = ReadNetworkConfig.getCA(org).getUrl();
        // TODO : properties , what can this do ?  如果是用的是https协议需要设置prop
        CAClient caClient = null;
        URL url = new URL(caUrl);
        if (StringUtils.equals("https", url.getProtocol())) {
            Properties caProp = new Properties();
            caProp.setProperty("pemFile", ReadNetworkConfig.getCA(org).getTlsCacerts());
            caProp.setProperty("allowAllHostNames", "true");   // "true" or "false"
            caClient = new CAClient(caUrl, caProp);
        } else {
            caClient = new CAClient(caUrl, null);
        }
        // Enroll Admin to Org1MSP
        UserContext adminUserContext = new UserContext();
        adminUserContext.setName(ADMIN);
        adminUserContext.setAffiliation(org);
        adminUserContext.setMspId(ReadNetworkConfig.getParam(NETWORK_CONFIG, PEER, org, MSPID));
        caClient.setAdminUserContext(adminUserContext);
        adminUserContext = caClient.enrollAdminUser(ADMIN, ADMIN_PASSWORD);

        return adminUserContext;
    }
}
